//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// 'Map properties' dialog, 'Statistics' tab.
//

# include "StdAfx.h"
# include "Resource.h"
# include "MapProperties.h"
# include "Map.h"
# include "Types.h"
# include "ListView.h"
# include "ParseHelpers.h"

static HWND g_hTypes;
static int g_iSortColumn_Stat = 0;
static bool g_bAccent_Stat = true;

enum {
	lvType        = 0,
	lvKind        = 1,
	lvDescription = 2,
	lvCount       = 3,
	lvSize        = 4
};

static
int CALLBACK _CompareItems_Stat (LPARAM _lParam1, LPARAM _lParam2, LPARAM) {
	char str1 [64], str2 [64];
	ListView_GetItemText (g_hTypes, _lParam1, g_iSortColumn_Stat, str1, 64); 
	ListView_GetItemText (g_hTypes, _lParam2, g_iSortColumn_Stat, str2, 64);

	if (g_iSortColumn_Stat == lvCount || g_iSortColumn_Stat == lvSize) {
		// Compare as integers.
		return g_bAccent_Stat ? ParseDecU (str1, str1 + 64) - ParseDecU (str2, str2 + 64) : ParseDecU (str2, str2 + 64) - ParseDecU (str1, str1 + 64);
	} else
		// Compare as strings.
		return g_bAccent_Stat ? ::strcmp (str1, str2) : ::strcmp (str2, str1);
}

struct ut_data_t {
	size_t cCounter;
	size_t cSize;

	ut_data_t () : cCounter (0), cSize (0) {}
};

static
void _FillStatistics (HWND _hWnd, size_t _cLevel) {
	CCritSec::CLock lock (g_cs);

	const size_t cTypes = g_map.pTypeSet->cTypes;
	const type_desc_t * const pTypes = g_map.pTypeSet->pTypes;
	const type_desc_t * const pUnknownTD = g_map.pTypeSet->GetUnknownTD ();

	//
	size_t cPoints = 0, cPolylines = 0, cPolygons = 0;
	size_t cMaxNodes = 0, cMaxElements = 0, cTotalNodes = 0;
	size_t cRGLinks = 0;

	size_t cLabels   = 0, cLabelsSize   = 0;
	size_t cComments = 0, cCommentsSize = 0;
	size_t cRgnsSize = 0;
	size_t cElementsTotal = 0, cElementsSize = 0;
	size_t cOriginsSize   = 0;
	size_t cPointsSize    = 0;
	size_t cPointsSize2   = 0;
	
	// The array of counters being parallel to pTypes.
	size_t * const pCounters = reinterpret_cast<size_t *> (alloca (sizeof (size_t)*cTypes));
	::memset (pCounters, 0, sizeof (size_t)*cTypes);
	size_t * const pSizes    = reinterpret_cast<size_t *> (alloca (sizeof (size_t)*cTypes));
	::memset (pSizes, 0, sizeof (size_t)*cTypes);

	typedef std::map<DWORD, ut_data_t> mapTypes_t;
	mapTypes_t mapUnknownTypes;
	mapTypes_t mapSpecificTypes;

	// Count types.
	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		const rgns_t & rgns = g_map.layers [cKind].rgns;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;

			bool bHasElements = false;

			// Elements.
			const size_t cElements = rgn.elements.size ();
			for (size_t cElement = 0; cElement < cElements; ++ cElement) {
				const rgn_element_t & element = rgn.elements [cElement];
				if (element.bDeleted)
					continue;

				if (
					_cLevel == -1 ||
					element.cLevel == _cLevel ||
					(rgn.uchEndLevel != 0 && element.cLevel <= _cLevel && _cLevel <= rgn.uchEndLevel)
				) {
					bHasElements = true;

					++ cElementsTotal;
					cElementsSize += sizeof (element);
					cPointsSize  += element.points.capacity ()*sizeof (point_t);
					cPointsSize2 += element.points.size ()*sizeof (point_t);

					const size_t cNodes = element.points.size ();
					cTotalNodes += cNodes;

					if (cNodes > cMaxNodes)
						cMaxNodes = cNodes;

					if (element.pExtra) {
						const point_extras_t & PointExtras = element.pExtra->PointExtras;
						const size_t cNodes2 = PointExtras.size ();
						size_t cRGNodesInElement = 0;
						for (size_t cNode = 0; cNode < cNodes2 && cNode < cNodes; ++ cNode) {
							const rgnode_t * const pNode = PointExtras [cNode].GetRGNode ();
							if (pNode != NULL)
								++ cRGNodesInElement;
						}
						if (cRGNodesInElement != 0)
							cRGLinks += cRGNodesInElement - 1;
					}
				}
			}

			// Origins.
			const size_t cOrigins = rgn.origins.size ();
			for (size_t cOrigin = 0; cOrigin < cOrigins; ++ cOrigin) {
				const rgn_origin_t & origin = rgn.origins [cOrigin];
				if (origin.bDeleted)
					continue;

				if (
					_cLevel == -1 ||
					origin.cLevel == _cLevel ||
					(rgn.uchEndLevel != 0 && origin.cLevel <= _cLevel && _cLevel <= rgn.uchEndLevel)
				) {
					bHasElements = true;

					++ cTotalNodes;

					cOriginsSize += sizeof (rgn_origin_t);
				}
			}

			if (! bHasElements)
				continue;

			if (cElements > cMaxElements)
				cMaxElements = cElements;

			// Count labels.
			if (! rgn.strLabel.empty ()) {
				++ cLabels;
				cLabelsSize += rgn.strLabel.size ();
			}

			// Count comments.
			if (! rgn.strComment.empty ()) {
				++ cComments;
				cCommentsSize += rgn.strComment.size ();
			}

			cRgnsSize += sizeof (rgn);

			if (rgn.IsPolyline ())
				++ cPolylines;
			else if (rgn.IsPolygon ())
				++ cPolygons;
			else
				++ cPoints;

			// Count types.
			const type_desc_t * const pTD = rgn.GetTypeDesc ();
			assert (pTD);
			if (pTD && ! pTD->bUnknown) {
				if (pTD->ushType == rgn.ushType) {
					++ (pCounters [pTD->cOrdinal]);
					if (rgn.cEndPos > rgn.cBeginPos)
						// NOTE: rgn.cEndPos may be incorrect for damaged files (w/o correct end).
						pSizes [pTD->cOrdinal] += rgn.cEndPos - rgn.cBeginPos;
				} else {
					const DWORD dwType = rgn.ushType + (DWORD (rgn.Kind) << 16);
					mapTypes_t::iterator i = mapSpecificTypes.find (dwType);
					if (i == mapSpecificTypes.end ())
						i = mapSpecificTypes.insert (mapTypes_t::value_type (dwType, ut_data_t ())).first;
					++ i->second.cCounter;
					if (rgn.cEndPos > rgn.cBeginPos)
						i->second.cSize += rgn.cEndPos - rgn.cBeginPos;
				}
			} else {
				const DWORD dwType = rgn.ushType + (DWORD (rgn.Kind) << 16);
				mapTypes_t::iterator i = mapUnknownTypes.find (dwType);
				if (i == mapUnknownTypes.end ())
					i = mapUnknownTypes.insert (mapTypes_t::value_type (dwType, ut_data_t ())).first;
				++ i->second.cCounter;
				if (rgn.cEndPos > rgn.cBeginPos)
					i->second.cSize += rgn.cEndPos - rgn.cBeginPos;
			}
		}
	}

	// Count RG nodes.
	g_map.CountRGNodes ();

	size_t cRGNodes = 0;
	for (rgnodes_t::const_iterator in = g_map.rgnodes.begin (); in != g_map.rgnodes.end (); ++ in) {
		const rgnode_t & node = * in;
		if (node.IsUsed ())
			++ cRGNodes;
	}

	// Count turn restictions.
	size_t cTurnRestrictions = 0;
	for (rgrestrictions_t::const_iterator ir = g_map.rgrestrictions.begin (); ir != g_map.rgrestrictions.end (); ++ ir) {
		const rgrestriction_t & restriction = ir->second;
		if (restriction.IsUsed ())
			++ cTurnRestrictions;
	}

	char strBuf [64];

	ListView_DeleteAllItems (g_hTypes);

	size_t cItem = 0;

	// For each type.
	// NOTE: omit 0-th type ("unknown").
	for (size_t cType = 1; cType < cTypes; ++ cType) {
		if (pCounters [cType] == 0)
			continue;

		// Code.
		InsertItem     (g_hTypes, cItem, 0,             "0x%04x", pTypes [cType].ulType & 0xFFFF);
		SetItemIcon    (g_hTypes, cItem, pTypes [cType].cImageListIndex);
		// Kind.
		assert (pTypes [cType].Kind < rkMax);
		SetSubItemText (g_hTypes, cItem, lvKind,        "%s", g_strKinds [pTypes [cType].Kind]);
		// Description.
		SetSubItemText (g_hTypes, cItem, lvDescription, "%s", pTypes [cType].strTypeName);
		// Count.
		SetSubItemText (g_hTypes, cItem, lvCount,       "%d", pCounters [cType]);
		// Size.
		SetSubItemText (g_hTypes, cItem, lvSize,        "%d", pSizes [cType]);

		++ cItem;
	}

	// Specific types.
	mapTypes_t::iterator i;
	for (i = mapSpecificTypes.begin (); i != mapSpecificTypes.end (); ++ i) {
		const USHORT ushType = i->first & 0xFFFF;
		const USHORT Kind    = i->first >> 16;
		assert (Kind < rkMax);

		const type_desc_t * pTD = g_map.pTypeSet->GetTypeDesc (ushType, Kind);
		if (pTD == NULL)
			pTD = pUnknownTD;

		// Code.
		InsertItem     (g_hTypes, cItem, 0,             "0x%04x", ushType);
		SetItemIcon    (g_hTypes, cItem, pTD->cImageListIndex);
		// Kind.
		SetSubItemText (g_hTypes, cItem, lvKind,        "%s", g_strKinds [Kind]);
		// Description.
		SetSubItemText (g_hTypes, cItem, lvDescription, "%s", pTD->strTypeName);
		// Count.
		SetSubItemText (g_hTypes, cItem, lvCount,       "%d", i->second.cCounter);
		// Size.
		SetSubItemText (g_hTypes, cItem, lvSize,        "%d", i->second.cSize);
	}

	// Unknown types.
	for (i = mapUnknownTypes.begin (); i != mapUnknownTypes.end (); ++ i) {
		const USHORT ushType = i->first & 0xFFFF;
		const USHORT Kind    = i->first >> 16;
		assert (Kind < rkMax);

		// Code.
		InsertItem     (g_hTypes, cItem, 0,             "0x%04x", ushType);
		SetItemIcon    (g_hTypes, cItem, pUnknownTD->cImageListIndex);
		// Kind.
		SetSubItemText (g_hTypes, cItem, lvKind,        "%s", g_strKinds [Kind]);
		// Description.
		SetSubItemText (g_hTypes, cItem, lvDescription, "%s", pUnknownTD->strTypeName);
		// Count.
		SetSubItemText (g_hTypes, cItem, lvCount,       "%d", i->second.cCounter);
		// Size.
		SetSubItemText (g_hTypes, cItem, lvSize,        "%d", i->second.cSize);

		++ cItem;
	}

	ListView_SortItemsEx (g_hTypes, _CompareItems_Stat, 0);

	AutoSizeColumns (g_hTypes);

	::sprintf (strBuf, "%d", cPoints);
	::SetDlgItemText (_hWnd, IDC_POINTS, strBuf);
	::sprintf (strBuf, "%d", cPolygons);
	::SetDlgItemText (_hWnd, IDC_POLYGONS, strBuf);
	::sprintf (strBuf, "%d", cPolylines);
	::SetDlgItemText (_hWnd, IDC_POLYLINES, strBuf);
	::sprintf (strBuf, "%d", cPoints + cPolygons + cPolylines);
	::SetDlgItemText (_hWnd, IDC_TOTAL, strBuf);

	::sprintf (strBuf, "%d", cMaxNodes);
	::SetDlgItemText (_hWnd, IDC_MAX_NODES, strBuf);
	::sprintf (strBuf, "%d", cMaxElements);
	::SetDlgItemText (_hWnd, IDC_MAX_ELEMENTS, strBuf);
	::sprintf (strBuf, "%d", cTotalNodes);
	::SetDlgItemText (_hWnd, IDC_TOTAL_NODES, strBuf);

	::sprintf (strBuf, "%d", cRGNodes);
	::SetDlgItemText (_hWnd, IDC_RGNODES, strBuf);
	::sprintf (strBuf, "%d", cRGLinks);
	::SetDlgItemText (_hWnd, IDC_RGLINKS, strBuf);
	::sprintf (strBuf, "%d", cTurnRestrictions);
	::SetDlgItemText (_hWnd, IDC_TURNRESTRICTIONS, strBuf);
}

INT_PTR CALLBACK _MapStatisticsProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			CCritSec::CLock lock (g_cs);

			// Levels.
			const HWND hLevel = ::GetDlgItem (_hWnd, IDC_MAP_LEVEL);
			::SendMessage (hLevel, CB_INSERTSTRING, 0, (LPARAM) "All");
			::SendMessage (hLevel, CB_SETITEMDATA,  0, -1);
			for (size_t cLevel = 0; cLevel < g_map.Levels.size (); ++ cLevel) {
				char strText [64];
				::sprintf (strText, "%d", cLevel);
				::SendMessage (hLevel, CB_INSERTSTRING, cLevel + 1, (LPARAM) strText);
				::SendMessage (hLevel, CB_SETITEMDATA,  cLevel + 1, cLevel);
			}
			::SendMessage (hLevel, CB_SETCURSEL, 0, 0);

			// Statistics.			
			g_hTypes = ::GetDlgItem (_hWnd, IDC_TYPES);

			ListView_SetExtendedListViewStyleEx (g_hTypes, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);

			::SetWindowLong (g_hTypes, GWL_STYLE, ::GetWindowLong (g_hTypes, GWL_STYLE) | LVS_SHAREIMAGELISTS);
			ListView_SetImageList (g_hTypes, g_map.pTypeSet->GetTypeIcons (), LVSIL_SMALL);

			AddColumn (g_hTypes, lvType,        "Code");
			AddColumn (g_hTypes, lvKind,        "Kind");
			AddColumn (g_hTypes, lvDescription, "Description");
			AddColumn (g_hTypes, lvCount,       "Count");
			AddColumn (g_hTypes, lvSize,        "Size");

			_FillStatistics (_hWnd, -1);
			
			return TRUE;
		}

		case WM_COMMAND:
			switch (LOWORD (_wParam)) {
				case IDC_MAP_LEVEL: {
					if (HIWORD (_wParam) == CBN_SELCHANGE) {
						const HWND hLevel = ::GetDlgItem (_hWnd, IDC_MAP_LEVEL);
						const size_t cSelIndex = ::SendMessage (hLevel, CB_GETCURSEL, 0, 0);
						const size_t cLevel = static_cast<size_t> (::SendMessage (hLevel, CB_GETITEMDATA, cSelIndex, 0));

						_FillStatistics (_hWnd, cLevel);

						return TRUE;
					}
					break;
				}
			}
			break;

		case WM_NOTIFY: {
			const NMHDR * const pInfo = reinterpret_cast<const NMHDR *> (_lParam);
			switch (pInfo->code) {
				case LVN_COLUMNCLICK:
					const NMLISTVIEW * const pInfo2 = reinterpret_cast<const NMLISTVIEW *> (_lParam);

					if (g_iSortColumn_Stat != pInfo2->iSubItem)
						g_iSortColumn_Stat = pInfo2->iSubItem;
					else
						g_bAccent_Stat = ! g_bAccent_Stat;
					
					ListView_SortItemsEx (g_hTypes, _CompareItems_Stat, 0);

					break;
			}
			break;
		}
	}

	return FALSE;
}
